// Includes
#include "Game.h"
#include "Sprite.h"		// Sprite header

#include <iostream>		// C++ I/O stream library
#include <cmath>		// C math library

// TEST
#include "GameObject.h"
#include "CDirectionalLight.h"
#include "CPositionalLight.h"
#include "CAmbientLight.h"

using namespace std;

namespace EraAstral
{
	namespace core
	{
		// Game object pointer for testing
		GameObject *objPtr = NULL, *objPtr2 = NULL, *objPtr3 = NULL, *objPtr4 = NULL;
		
		// Game class constructor
		Game::Game()
		{
			// Set null pointers
			m_device = NULL;
			m_eventHandler = NULL;
			m_input = NULL;
			m_logger = NULL;
			m_timer = NULL;
			m_smgr = NULL;
			
			// Set shouldRun flag
			m_shouldRun = true;
			
			// Initial settings
			m_vSync = true;				// vSync enabled
			m_screenRefresh = 60;		// 60 hZ standard screen refresh value
				
			// Create timer
			m_timer = new Timer();
			// Timer should be first component in vector
			components.push_back(m_timer);
			m_timer->Start();
			// Create logger
			m_logger = new Logger();
			
			// Create device
			m_device = graphics::CreateDevice(m_logger);
			if (!m_device)
			{
				// Error attempting to create device
				cout << "Error creating graphics device." << endl;
			}
			else
			{
				cout << "Graphics device created." << endl;
			
				// Create event handler
				m_eventHandler = new EventHandler();
				// Create input device
				m_input = new InputDevice();
				// Register objects affected by events
				m_eventHandler->RegisterAffectedObject(this);
				m_eventHandler->RegisterAffectedObject(m_device);
				m_eventHandler->RegisterAffectedObject(m_input);
				
				// Create scene manager
				m_smgr = new SceneManager(m_device);
			}
		}
		
		// Game class destructor
		Game::~Game()
		{
			// Delete kernel components
			if (!components.empty())
			{
				// Loop through kernel components
				for (unsigned int i = 0; i < components.size(); i++)
				{
					// Delete components
					if (components[i] != NULL)
						delete components[i];
				}
				
				// Clear vector
				components.clear();
			}
			
			// Delete scene manager
			if (m_smgr != NULL)
				delete m_smgr;
		
			// Delete graphics device
			if (m_device != NULL)
				delete m_device;
				
			// Delete input device
			if (m_input != NULL)
				delete m_input;
			
			// Delete event handler
			if (m_eventHandler != NULL)
				delete m_eventHandler;
				
			if (m_logger != NULL)
			{
				// Flush log entries to file
				m_logger->FlushToFile("log.txt");
				// Delete logger object
				delete m_logger;
			}
		}
		
		// Initializes the game and starts the game kernel
		void Game::Run()
		{
			// Check if device was created properly
			if (!m_device)
			{
				// Post error message to log and return
				m_logger->PostEntry(LE_ERROR, "Error creating graphics device.");
				return;
			}
		
			// Initialize game
			if (Initialize() != 0)
			{
				cout << "Error during game initialization." << endl;
				m_logger->PostEntry(LE_ERROR, "Error during game initialization.");
				return;
			}
			
			cout << "Game initialized. Starting game kernel." << endl;
			m_logger->PostEntry(LE_MESSAGE, "Game initialized. Starting game kernel.");

			// Game kernel
			while (m_shouldRun)
			{
				// Process events
				m_eventHandler->ProcessEvents();
				// Perform update logic
				Update();
				
				// Check if kernel run flag was cleared during CPU phase
				if (!m_shouldRun)
					break;

				// Begin frame
				m_device->BeginFrame();
				// Render game graphics
				Render();
				// End frame
				m_device->EndFrame();
				
				// Notify input device that a new frame will start
				m_input->NotifyNewFrame();
			}
			
			cout << "Game kernel terminated." << endl;
			m_logger->PostEntry(LE_MESSAGE, "Game kernel terminated.");
		}
		
		// Exit game
		void Game::Exit()
		{
			// Stop timer
			m_timer->Stop();
			// Clear kernel run flag
			m_shouldRun = false;
		}
		
		// Process events sent by event handler
		void Game::ProcessEventSignal(const Event *event)
		{
			// Exit game on quit signal
			if (event->type == EVENT_QUIT)
				Exit();
		}

		// Game initialization
		int Game::Initialize()
		{
			// TEST		
			
			GameObject *test = new GameObject();
			objPtr = test;
			test->LoadMesh("untitled3.obj");
			if (test->GetMesh() == NULL)
				cout << "Error loading mesh..." << endl;
			else
			{
				cout << "Vertex count: " << test->GetMesh()->GetVertexCount() << endl;
				test->GetMesh()->LoadTexture("Himmel.jpg");
			}

			m_smgr->AddObject(test);
			
			GameObject *test2 = new GameObject();
			objPtr2 = test2;
			test2->LoadMesh("untitled3.obj");
			test2->SetPosition(Vector3(2.0f, 0.0f, -6.0f));
			m_smgr->AddObject(test2);
			test2->GetMesh()->LoadTexture("Himmel.jpg");
			
			GameObject *test3 = new GameObject();
			objPtr3 = test3;
			test3->LoadMesh("untitled3.obj");
			test3->SetPosition(Vector3(-8.0f, 0.0f, -2.0f));
			m_smgr->AddObject(test3);
			test3->GetMesh()->LoadTexture("Himmel.jpg");
			
			GameObject *test4 = new GameObject();
			objPtr4 = test4;
			test4->LoadMesh("untitled3.obj");
			test4->SetPosition(Vector3(-3.5f, 0.0f, 2.0f));
			m_smgr->AddObject(test4);
			test4->GetMesh()->LoadTexture("Himmel.jpg");
			
			m_smgr->GetCamera()->SetPosition(Vector3(0.0f, 10.0f, 8.0f));
			m_smgr->GetCamera()->SetTarget(Vector3(-2.0f, 0.0f, 0.0f));
			
			// Set up lights
			AmbientLight *amb = new AmbientLight(m_smgr);
			amb->SetColor(Color4f(0.2f, 0.2f, 0.2f, 0.2f));
			amb->Commit();

			PositionalLight *pos = new PositionalLight(m_smgr);
			pos->SetColor(Color4f(1.0f, 0.2f, 0.2f, 1.0f));
			pos->SetPosition(Vector3(5.0f, 5.0f, 2.0f));
			pos->Commit();

			PositionalLight *pos2 = new PositionalLight(m_smgr);
			pos2->SetColor(Color4f(0.2f, 0.2f, 1.0f, 1.0f));
			pos2->SetPosition(Vector3(-5.0f, 5.0f, 2.0f));
			pos2->Commit();

			PositionalLight *pos3 = new PositionalLight(m_smgr);
			pos3->SetColor(Color4f(0.2f, 1.0f, 0.2f, 1.0f));
			pos3->SetPosition(Vector3(0.0f, -5.0f, 2.0f));
			pos3->Commit();
			
			SDL_ShowCursor(SDL_DISABLE);
			
			// Exit successfully
			return 0;
		}

		// Game update logic
		void Game::Update()
		{
			// Update game components
			// First component should always be the game timer
			for (unsigned int i = 0; i < components.size(); i++)
				components[i]->Update(m_timer->GetTicksSinceLast());
				
			// For testing: Exit on escape press
			if (m_input->IsKeyDown(SDLK_ESCAPE))
				Exit();
			
			// Test input section
			if (m_input->IsKeyDown(SDLK_SPACE) && m_input->WasKeyUp(SDLK_SPACE))
			{
				static int polygonFill = 1;
				GLenum polygonEnum = polygonFill == 1 ? GL_LINE : (polygonFill == 2 ? GL_POINT : GL_FILL);
				polygonFill++;
				if (polygonFill == 4)
					polygonFill = 1;
				glPolygonMode(GL_FRONT_AND_BACK, polygonEnum);
			}
			if (m_input->IsKeyDown(SDLK_l) && m_input->WasKeyUp(SDLK_l))
			{
				m_smgr->ToggleLighting();
			}	
			if (m_input->IsKeyDown(SDLK_a) && m_input->WasKeyUp(SDLK_a))
			{
				m_smgr->GetLight(1)->SetEnabled(!m_smgr->GetLight(1)->IsEnabled());
			}
			if (m_input->IsKeyDown(SDLK_s) && m_input->WasKeyUp(SDLK_s))
			{
				m_smgr->GetLight(2)->SetEnabled(!m_smgr->GetLight(2)->IsEnabled());
			}
			if (m_input->IsKeyDown(SDLK_d) && m_input->WasKeyUp(SDLK_d))
			{
				m_smgr->GetLight(3)->SetEnabled(!m_smgr->GetLight(3)->IsEnabled());
			}
			
			*objPtr->GetRotation() += Vector3((float)m_input->GetMouseY() - 240.0f, 
												(float)m_input->GetMouseX() - 320.0f, 0.0f);
			
			*objPtr2->GetRotation() += Vector3(((float)m_input->GetMouseY() - 240.0f) * 2.0f, 
												(float)m_input->GetMouseX() - 320.0f, 0.0f);
			
			*objPtr3->GetRotation() += Vector3((float)m_input->GetMouseY() - 240.0f, 
												((float)m_input->GetMouseX() - 320.0f) * 0.6f, 0.0f);
			
			*objPtr4->GetRotation() += Vector3(((float)m_input->GetMouseY() - 240.0f) * 1.8f, 
												((float)m_input->GetMouseX() - 320.0f) * 0.2f, 0.0f);
			SDL_WarpMouse(320, 240);
		}

		// Game rendering logic
		void Game::Render()
		{
			// Draw everything from the scene manager
			m_smgr->DrawAll();		
		
			// Draw game components
			for (unsigned int i = 0; i < components.size(); i++)
			{
				if (components[i]->IsDrawable())
					components[i]->Draw();
			}
			
			// Apply vSync
			if (m_vSync)
				ApplyVSync();
		}
		
		// Returns a pointer to the game logger
		Logger *Game::GetLogger() const
		{
			return m_logger;
		}
		
		// Waits for next screen refresh
		void Game::ApplyVSync()
		{
			int refreshOffset = m_timer->GetTicksSinceLast() - (1000 / m_screenRefresh);
		
			// Are we about to move a frame before screen refresh?
			if (refreshOffset < 0)
			{
				// Sleep until next update
				unsigned int sleepTime = abs(refreshOffset);
				m_timer->Sleep(sleepTime);
			}
		}
	}
}
